/*
 * fat12_tool.c
 *
 *  Created on: 2016-9-26
 *      Author: m
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define dprintf(fmt, ...) printf("%s:%d " fmt, __FILE__, __LINE__, ##__VA_ARGS__)

#define PRINT_RAW_STR(field, field_name) { \
        char str[sizeof(field) + 1] = {0}; \
        memcpy(str, field, sizeof(field)); \
        dprintf("%20s %s\n", #field_name, str); \
    }

//19*512 = 0x2600
#define ROOT_ITEM_ADDR(img_base, idx) ((char*)(img_base) + 0x2600 + 32 * (idx))
#define FAT_BASE_ADDR(img_base) ((char*)(img_base) + 0x200)
#define DATA_ADDR(img_base, BPB_RootEntCnt, cluster_no) ((char*)(img_base) + 0x2600 + (BPB_RootEntCnt) * 32 + ((cluster_no) - 2) * 512)

#define FAT12_ATTR_COM          (0x00) //00000000：普通文件，可随意读写
#define FAT12_ATTR_READ_ONLY    (0x01) //00000001：只读文件，不可改写
#define FAT12_ATTR_HIDE         (0x02) //00000010：隐藏文件，浏览文件时隐藏列表
#define FAT12_ATTR_SYS          (0x04) //00000100：系统文件，删除的时候会有提示
#define FAT12_ATTR_VOL          (0x08) //00001000：卷标，作为磁盘的卷标识符
#define FAT12_ATTR_DIR          (0x10) //00010000：目录文件，此文件是一个子目录，它的内容就是此目录下的所有文件目录项
#define FAT12_ATTR_ARCH         (0x20) //00100000：归档文件

typedef struct {
    char BS_jmpBoot[3];//          0           3       短跳转                     jmp $
    char BS_OEMName[8];//          3           8       厂商名                     'NilClass'
    short BPB_BytesPerSec;//       11(0xb)     2       每扇区字节数                512(0x200)
    char BPB_SecPerClus;//         13(0xd)     1       每簇扇区数                  1
    short BPB_RsvdSecCnt;//        14(0xe)     2       Boot记录占用扇区个数         1  (一说FAT起始位置)
    char BPB_NumFATs;//            16(0x10)    1       FAT表个数                  2
    short BPB_RootEntCnt;//        17(0x11)    2       根目录文件数最大值           224(0xe0)
    short BPB_TotSec16;//          19(0x13)    2       扇区总数                   2880(0xb40 1.44M)
    char BPB_Media;//              21(0x15)    1       介质描述符(磁盘种类)         0xF0
    short BPB_FATSz16;//           22(0x16)    2       每FAT占用扇区数             9
    short BPB_SecPerTrk;//         24(0x18)    2       每磁道(柱面)扇区数           18(0x12)
    short BPB_NumHeads;//          26(0x1A)    2       磁头个数(面数)              2
    int BPB_HideSec;//             28(0x1C)    4       隐藏扇区数(一说不使用分区)    0
    int BPB_TotSec32;//            32(0x20)    4       BPB_TotSec16的备份         0
    char BS_DrvNum;//              36(0x24)    1       中断13的驱动器号            0
    char BS_Reserved;//            37(0x25)    1       保留                      0
    char BS_BootSig;//             38(0x26)    1       扩展引导标记                0x29
    int BS_VolID;//                39(0x27)    4       卷序列号                   0
    char BS_VolLab[11];//          43(0x2B)    11      卷标                      'Pepper@2016'
    char BS_FileSysType[8];//      54(0x36)    8       文件系统类型                'FAT12   '
    //.引导代码及其它        62(0x3E)    448     主引导代码                 -
    char boot_code[448];
    char tail_flag[2];
    //.结束标志            510(0x1FE)  2       0xAA55                   0xAA55
} __attribute__((packed)) FAT12_SEC0;

typedef struct {
    //名称             偏移    长度      描述
    char DIR_Name[11];//        0       0xB     文件名8字节，扩展名3字节
    char DIR_Attr;//        0xB     1       文件属性
    char reserved[10];//;保留位           0xC     10      -
    short DIR_WriteTime;//   0x16    2       最后一次写入时间
    short DIR_WriteDate;//   0x18    2       最后一次写入日期
    short DIR_FirstClus;//   0x1A    2       此条目对应的开始簇号
    int DIR_FileSize;//    0x1C    4       文件大小
} __attribute__((packed)) DIR_ITEM;

void show_help() {
    printf("usage:\n");
    printf("  -h img: show sector 0 info\n");
    printf("  -c img: create a new virtual-floppy-disk and format it.\n");
    printf("  -a img file: add a file to virtual-floppy-disk.\n");
}

int show_sector0_info(char* vd) {
    FILE* f = 0;
    FAT12_SEC0 h;

    f = fopen(vd, "rb");
    if(!f) {
        dprintf("open %s failed!\n", vd);
        return -1;
    }

    if(1 != fread(&h, sizeof(h), 1, f)) {
        dprintf("read %s failed!\n", vd);
        fclose(f);
        return -1;
    }

    dprintf("               Field Value\n");
    PRINT_RAW_STR(h.BS_OEMName, BS_OEMName); //          3           8       厂商名                     'NilClass'
    dprintf("     BPB_BytesPerSec %d\n", h.BPB_BytesPerSec);//       11(0xb)     2       每扇区字节数                512(0x200)
    dprintf("      BPB_SecPerClus %d\n", h.BPB_SecPerClus);//         13(0xd)     1       每簇扇区数                  1
    dprintf("      BPB_RsvdSecCnt %d\n", h.BPB_RsvdSecCnt);//        14(0xe)     2       Boot记录占用扇区个数         1  (一说FAT起始位置)
    dprintf("         BPB_NumFATs %d\n", h.BPB_NumFATs);//            16(0x10)    1       FAT表个数                  2
    dprintf("      BPB_RootEntCnt %d\n", h.BPB_RootEntCnt);//        17(0x11)    2       根目录文件数最大值           224(0xe0)
    dprintf("        BPB_TotSec16 %d\n", h.BPB_TotSec16);//          19(0x13)    2       扇区总数                   2880(0xb40 1.44M)
    dprintf("           BPB_Media 0x%X\n", h.BPB_Media&0xFF);//              21(0x15)    1       介质描述符(磁盘种类)         0xF0
    dprintf("         BPB_FATSz16 %d\n", h.BPB_FATSz16);//           22(0x16)    2       每FAT占用扇区数             9
    dprintf("       BPB_SecPerTrk %d\n", h.BPB_SecPerTrk);//         24(0x18)    2       每磁道(柱面)扇区数           18(0x12)
    dprintf("        BPB_NumHeads %d\n", h.BPB_NumHeads);//          26(0x1A)    2       磁头个数(面数)              2
    dprintf("         BPB_HideSec %d\n", h.BPB_HideSec);//             28(0x1C)    4       隐藏扇区数(一说不使用分区)    0
    dprintf("        BPB_TotSec32 %d\n", h.BPB_TotSec32);//            32(0x20)    4       BPB_TotSec16的备份         0
    dprintf("           BS_DrvNum %d\n", h.BS_DrvNum);//              36(0x24)    1       中断13的驱动器号            0
    dprintf("          BS_BootSig 0x%X\n", h.BS_BootSig&0xFF);//             38(0x26)    1       扩展引导标记                0x29
    dprintf("            BS_VolID %d\n", h.BS_VolID);//                39(0x27)    4       卷序列号                   0
    PRINT_RAW_STR(h.BS_VolLab, BS_VolLab); //          43(0x2B)    11      卷标                      'Pepper@2016'
    PRINT_RAW_STR(h.BS_FileSysType, BS_FileSysType);//      54(0x36)    8       文件系统类型                'FAT12   '
    return 0;
}

void set_fat(int idx, short val,char* fat_addr) {
    unsigned short o = 0;
    unsigned short* p = 0;
    if (0 == idx % 2) {
        p = (unsigned short*)(fat_addr + (idx / 2) * 3);
        o = *p;
        *p = (o & 0xF000) | (val & 0xFFF);
        *((unsigned short*)((char*)p+9*512)) = (o & 0xF000) | (val & 0xFFF);//fat2
    } else {
        p = (unsigned short*)(fat_addr + (idx / 2) * 3 + 1);
        o = *p;
        *p = (o & 0xF) | ((val & 0xFFF) << 4);
        *((unsigned short*)((char*)p+9*512)) = (o & 0xF) | ((val & 0xFFF) << 4);//fat2
    }
}

void set_root_item_DIR_Name(char* pitem, char* DIR_Name_main, char* DIR_Name_ext) {
    int l_main = strlen(DIR_Name_main);
    int l_ext = strlen(DIR_Name_ext);

    l_main = (l_main > 8) ? 8 : l_main;
    l_ext = (l_ext > 3) ? 3 : l_ext;
    memcpy(pitem, "            ", 11);
    memcpy(pitem, DIR_Name_main, l_main);
    memcpy(pitem + 8, DIR_Name_ext, l_ext);
}
void set_root_item_DIR_Attr(char* pitem, char DIR_Attr) {
    unsigned char* pAttr = &pitem[11];
    *pAttr = DIR_Attr;
}
void set_root_item_DIR_WriteTime(char* pitem, unsigned short DIR_WriteTime, unsigned short DIR_WriteDate) {
    unsigned short* pt = (unsigned short*)&pitem[0x16];
    unsigned short* pd = (unsigned short*)&pitem[0x18];
    *pt = DIR_WriteTime;
    *pd = DIR_WriteDate;
}
void set_root_item_DIR_FirstClus(char* pitem, unsigned short DIR_FirstClus) {
    unsigned short* pfc = (unsigned short*)&pitem[0x1A];
    *pfc = DIR_FirstClus;
}
void set_root_item_DIR_FileSize(char* pitem, unsigned int DIR_FileSize) {
    unsigned int* pfsz = (unsigned int*)&pitem[0x1C];
    *pfsz = DIR_FileSize;
}

int create_new_disk(char* vd, char* vol_label, char* oem_name) {
    FAT12_SEC0 h;
    FILE* f = 0;
    char* img = 0;
    int l = 0;

    //assert(512, sizeof(tail_flag))
    memset(&h, 0, sizeof(h));
    h.BS_jmpBoot[0] = 0xEB; //jmp $
    h.BS_jmpBoot[1] = 0xFE;
    h.BS_jmpBoot[2] = 0x90; //nop

    l = strlen(oem_name);
    l = (l > 8) ? 8 : l;
    memcpy(h.BS_OEMName, "            ", 8);
    memcpy(h.BS_OEMName, oem_name, l);
    h.BPB_BytesPerSec = 0x200; //512
    h.BPB_SecPerClus = 1;
    h.BPB_RsvdSecCnt = 1;//        14(0xe)     2       Boot记录占用扇区个数         1  (一说FAT起始位置)
    h.BPB_NumFATs = 2;//            16(0x10)    1       FAT表个数                  2
    h.BPB_RootEntCnt = 0xE0;//        17(0x11)    2       根目录文件数最大值           224(0xe0)
    h.BPB_TotSec16 = 0xB40;//          19(0x13)    2       扇区总数                   2880(0xb40 1.44M)
    h.BPB_Media = 0xF0;//              21(0x15)    1       介质描述符(磁盘种类)         0xF0
    h.BPB_FATSz16 = 9;//           22(0x16)    2       每FAT占用扇区数             9
    h.BPB_SecPerTrk = 0x12;//         24(0x18)    2       每磁道(柱面)扇区数           18(0x12)
    h.BPB_NumHeads = 2;//          26(0x1A)    2       磁头个数(面数)              2
    h.BPB_HideSec = 0;//             28(0x1C)    4       隐藏扇区数(一说不使用分区)    0
    h.BPB_TotSec32 = 0;//            32(0x20)    4       BPB_TotSec16的备份         0
    h.BS_DrvNum = 0;//              36(0x24)    1       中断13的驱动器号            0
    h.BS_Reserved = 0;//            37(0x25)    1       保留                      0
    h.BS_BootSig = 0x29;//             38(0x26)    1       扩展引导标记                0x29
    h.BS_VolID = 0;//                39(0x27)    4       卷序列号                   0
    l = strlen(vol_label);
    l = (l > 11) ? 11 : l;
    memcpy(h.BS_VolLab, "            ", 11);
    memcpy(h.BS_VolLab, vol_label, l);
    memcpy(h.BS_FileSysType, "FAT12   ", 8);//      54(0x36)    8       文件系统类型                'FAT12   '
    h.tail_flag[0] = 0x55;
    h.tail_flag[1] = 0xAA;

    f = fopen(vd, "w");
    if(!f) {
        dprintf("open %s failed!\n", vd);
        return -1;
    }

    img = (char*)malloc(1440 * 1024); //1.44M
    memset(img, 0, 1440 * 1024);
    memcpy(img, &h, sizeof(h));

    set_fat(0, 0xFF0, &img[512]);
    set_fat(1, 0xFFF, &img[512]);

    memcpy(ROOT_ITEM_ADDR(img, 0), h.BS_VolLab, 11);
    set_root_item_DIR_Attr(ROOT_ITEM_ADDR(img, 0), FAT12_ATTR_ARCH | FAT12_ATTR_VOL);
    set_root_item_DIR_WriteTime(ROOT_ITEM_ADDR(img, 0), 0x0000, 0x0000);
    set_root_item_DIR_FirstClus(ROOT_ITEM_ADDR(img, 0), 0x0000);
    set_root_item_DIR_FileSize(ROOT_ITEM_ADDR(img, 0), 0x00000000);

    if(1 != fwrite(img, 1440 * 1024, 1, f)) {
        dprintf("write %s file failed!\n", vd);
        free(img);
        fclose(f);
        return -1;
    }
    free(img);
    fclose(f);

    return 0;
}

//ret 1:enough  0:no space to save new file.
int sub_check_enough_free_cluster(char* fat_base, int need) {
    int i = 0;
    int cnt = 0;
    for(; i < 9 * 512 / 3; ++i) {
        unsigned short it1, it2;
        it1 = (fat_base[i * 3] | (((fat_base[i * 3 + 1] & 0xF) << 8) & 0xF00)) & 0xFFF; //low | high
        it2 = (((fat_base[i * 3 + 1] >> 4) & 0xF) | ((fat_base[i * 3 + 2] << 4) & 0xFF0));//low | high
        if(0 == it1) {
            ++cnt;
        }
        if(0 == it2) {
            ++cnt;
        }
        if(cnt >= need) {
            return 1;
        }
    }
    return 0;
}

//ret next 12-bit cluster-using-info index.
int sub_get_next_free_cluster(char* fat_base, int after) {
    int i = after / 2;
    for(; i < 9 * 512 / 3; ++i) {
        unsigned short it1, it2;
        it1 = (fat_base[i * 3] | (((fat_base[i * 3 + 1] & 0xF) << 8) & 0xF00)) & 0xFFF; //low | high
        it2 = (((fat_base[i * 3 + 1] >> 4) & 0xF) | ((fat_base[i * 3 + 2] << 4) & 0xFF0));//low | high
        if(0 == it1 && (i * 2 > after)) {
            return i * 2;
        }
        if(0 == it2 && (i * 2 + 1 > after)) {
            return i * 2 + 1;
        }
    }
    return 0;
}

int sub_add_file(char* img, char* buf, int len, char* dir_name_main, char* dir_name_ext) {
    int c = sub_get_next_free_cluster(FAT_BASE_ADDR(img), 0);
    int nextc = sub_get_next_free_cluster(FAT_BASE_ADDR(img), c);
    FAT12_SEC0* ph = (FAT12_SEC0*)img;

    set_root_item_DIR_Name(ROOT_ITEM_ADDR(img, c - 1), dir_name_main, dir_name_ext); // c:start@2 first_root_dir:start@1
    set_root_item_DIR_Attr(ROOT_ITEM_ADDR(img, c - 1), FAT12_ATTR_COM);
    set_root_item_DIR_WriteTime(ROOT_ITEM_ADDR(img, c - 1), 0x1234, 0x5678);
    set_root_item_DIR_FirstClus(ROOT_ITEM_ADDR(img, c - 1), c);
    set_root_item_DIR_FileSize(ROOT_ITEM_ADDR(img, c - 1), len);


    while(len > 512) {
        set_fat(c, nextc, &img[512]);
        memcpy(DATA_ADDR(img, ph->BPB_RootEntCnt, c), buf, 512);
        c = nextc;
        nextc = sub_get_next_free_cluster(FAT_BASE_ADDR(img), c);
        len -= 512;
        buf += 512;
    }

    memcpy(DATA_ADDR(img, ph->BPB_RootEntCnt, c), buf, len);
    set_fat(c, 0xFFF, &img[512]);
}

char* last_str(char* s, int flag) {
    char* last_s = strrchr(s, flag);

    if(0 == last_s) { //if not exit
        return s;
    }
    return last_s + 1;
}

char* sub_fn_main(char* fn) {
    static char ms[12] = {0};
    int i = 0;

    memset(ms, 0, sizeof(ms));
    for(; i < 8; ++i) {
        if((fn[i] != '.') && (fn[i] != '\0')) {
            ms[i] = fn[i];
        } else {
            break;
        }
    }
    return ms;
}

char* sub_fn_ext(char* fn) {
    char* e = last_str(fn, '.');
    if(e == fn) { // no ext
        return "";
    } else {
        return e;
    }
}

char* str_UP(char* src) {
    char* s = src;
    while(*s != '\0') {
        *s = toupper(*s);
        s++;
    }
    return src;
}

int add_file(char* vd, char* full_fn) {
    FILE* f = fopen(vd, "rb+");
    FILE* fsrc = fopen(full_fn, "rb");;
    char* img = 0;
    int buflen = 0;
    char* buf = 0;
    char* fn = 0;


    if(!fsrc) {
        dprintf("open %s failed!\n", full_fn);
        return -1;
    }
    fseek(fsrc, 0, SEEK_END);
    buflen = ftell(fsrc);
    fseek(fsrc, 0, SEEK_SET);
    buf = (char*)malloc(buflen);
    memset(buf, 0, buflen);
    if(1 != fread(buf, buflen, 1, fsrc)) {
        dprintf("read %s failed!\n", full_fn);
        fclose(fsrc);
        free(buf);
        return -1;
    }
    fclose(fsrc);

    if(!f) {
        dprintf("open %s failed!\n", vd);
        free(buf);
        return -1;
    }

    img = (char*)malloc(1440 * 1024); //1.44M
    memset(img, 0, 1440 * 1024);
    if(1 != fread(img, 1440 * 1024, 1, f)) {
        dprintf("read %s failed!\n", vd);
        free(img);
        free(buf);
        fclose(f);
        return -1;
    }
    if(0 == sub_check_enough_free_cluster(FAT_BASE_ADDR(img), 2)) {
        dprintf("virtual-disk %s is full.\nadd file failed!\n", vd);
        free(img);
        free(buf);
        fclose(f);
        return -1;
    }

    fn = last_str(full_fn, '/');
    fn = str_UP(fn);
    sub_add_file(img, buf, buflen, sub_fn_main(fn), sub_fn_ext(fn));
    free(buf);
    fseek(f, 0, SEEK_SET);
    if(1 != fwrite(img, 1440 * 1024, 1, f)) {
        dprintf("write %s file failed!\n", vd);
        free(img);
        fclose(f);
        return -1;
    }

    free(img);
    fclose(f);
}
char* beautify_dir_name(char* raw_dn) {//8+3
    static char bu[13] = {0};
    int i = 0;
    int j = 0;

    memset(bu, 0, sizeof(bu));
    for(i = 7; i >= 0; --i) {
        if(' ' != raw_dn[i]) {
            break;
        }
    }
    i += 1; //copy-len
    memcpy(bu, raw_dn, i);

    for(j = 10; j >= 8; --j) {
        if(' ' != raw_dn[j]) {
            break;
        }
    }
    j -= 7; //copy-len
    if(j > 0) {
        strcat(bu, ".");
        memcpy(&bu[i + 1], &raw_dn[8], j);
    }
    return bu;
}

int sub_list_dir(char* img) {
    int i = 0;
    FAT12_SEC0* ph = (FAT12_SEC0*)img;

    printf("      DIR_Name    Attr   WriteTime   WriteDate  FirstClus FileSize\n");
    for(; i < ph->BPB_RootEntCnt; ++i) {
        DIR_ITEM* pdi = (DIR_ITEM*)ROOT_ITEM_ADDR(img, i);
        if(0 == pdi->DIR_Name[0]) {
            break;
        }
        printf("%15s ", beautify_dir_name(pdi->DIR_Name));

        //printf("0x%02X     ", pdi->DIR_Attr);
        if(FAT12_ATTR_COM == pdi->DIR_Attr) {
            printf("Common ");
        } else {
            if(FAT12_ATTR_READ_ONLY & pdi->DIR_Attr) {printf("R");} else {printf(" ");}
            if(FAT12_ATTR_HIDE & pdi->DIR_Attr) {printf("H");} else {printf(" ");}
            if(FAT12_ATTR_SYS & pdi->DIR_Attr) {printf("S");} else {printf(" ");}
            if(FAT12_ATTR_VOL & pdi->DIR_Attr) {printf("V");} else {printf(" ");}
            if(FAT12_ATTR_DIR & pdi->DIR_Attr) {printf("D");} else {printf(" ");}
            if(FAT12_ATTR_ARCH & pdi->DIR_Attr) {printf("A");} else {printf(" ");}
        }
        printf("   ");

        printf("0x%04X      ", pdi->DIR_WriteTime);
        printf("0x%04X     ", pdi->DIR_WriteDate);
        printf("0x%04X    ", pdi->DIR_FirstClus);
        printf("0x%08X(%d)\n", pdi->DIR_FileSize, pdi->DIR_FileSize);
    }

    return 0;
}

int list_dir(char* vd) {
    FILE* f = fopen(vd, "rb");
    char* img = (char*)malloc(1440 * 1024);
    if(!f) {
        dprintf("open %s failed!\n", vd);
        free(img);
        return -1;
    }

    if(1 != fread(img, 1440 * 1024, 1, f)) {
        dprintf("read %s failed!\n", vd);
        free(img);
        fclose(f);
        return -1;
    }
    fclose(f);
    sub_list_dir(img);
    free(img);
    return 0;
}

int main(int argc, char* argv[]) {
    if(3 == argc && 0 == strcmp("-h", argv[1])) {
        show_sector0_info(argv[2]);
    } else if(argc > 1 && 0 == strcmp("-c", argv[1])) {
        create_new_disk(argv[2], "myLabV", "NilCla");
    } else if(argc > 1 && 0 == strcmp("-a", argv[1])) {
        add_file(argv[2], argv[3]);
    } else if(argc > 1 && 0 == strcmp("-ls", argv[1])) {
        list_dir(argv[2]);
    } else {
        show_help();
    }
    return 0;
}
